import AsyncLock from "async-lock";
import { Subject } from "rxjs";

import { isValid } from "qqlx-cdk";

type QueueId = string;
type QueueJob = {
  release: Subject<void>;
  success: boolean;
  uid?: number | string;
};
export class SyncLock {
  constructor() {}

  private JobQueueMap = new Map<QueueId, AsyncLock>();
  private JobLine = new Map<QueueId, QueueJob[]>();

  getJobQueue(qid: QueueId): AsyncLock {
    const exist = this.JobQueueMap.get(qid);
    if (exist) return exist;
    else {
      const lock = new AsyncLock();
      this.JobQueueMap.set(qid, lock);
      return lock;
    }
  }

  lock(qid: QueueId, timeout = 1000 * 60) {
    return new Promise<void>((resolve, reject) => {
      const queue = this.getJobQueue(qid);

      // 超时处理
      const timer = setTimeout(() => {
        reject({ message: `处理超时（SyncLock）` });
        this._clearJobLine(qid);
        // throw { message: `处理超时（SyncLock）` }; // 不知道为什么 throw 语句外部无法捕获
      }, timeout);

      // 释放锁的Promise
      const $emit = new Subject<void>();
      $emit.subscribe(() => {
        queue.acquire("qid", () => {
          clearTimeout(timer);
          resolve();
        });
      });

      const line = this._getJobLine(qid);
      if (line.length === 0) {
        const job: QueueJob = { release: $emit, success: true };
        line.push(job);

        clearTimeout(timer);
        resolve();
      } else {
        const job: QueueJob = { release: $emit, success: false };
        line.push(job);
      }
    });
  }

  unlock(qid: QueueId) {
    const queue = this.getJobQueue(qid);
    queue.acquire(qid, () => {
      const match: { job?: QueueJob } = { job: this._spliceFirstJob(qid) };

      while (!!match.job) {
        if (match.job.success) {
          match.job = this._spliceFirstJob(qid);
          continue;
        } else {
          match.job.release.next();
          delete match.job;
          break;
        }
      }
    });
  }

  isLocking(qid: QueueId) {
    const length = this._getJobLine(qid).length;
    return {
      count: length,
      value: length > 0,
    };
  }

  private _spliceFirstJob(qid: string) {
    const line = this._getJobLine(qid);
    return line.splice(0, 1)[0];
  }

  private _getJobLine(qid: QueueId) {
    const queue = this.JobLine.get(qid);
    if (isValid(queue)) return queue as QueueJob[];
    else {
      const queue: QueueJob[] = [];
      this.JobLine.set(qid, queue);
      return queue;
    }
  }

  private _clearJobLine(qid: QueueId) {
    const queue = this.getJobQueue(qid);
    queue.acquire(qid, () => {
      this.JobLine.set(qid, []);
    });
  }
}
